package cipher

import (
	"unsafe"

	"github.com/dromara/dongle"
)

// CipherMode 定义分组模式枚举类型
type CipherMode string

// 分组模式常量
const (
	CBC CipherMode = "cbc"
	ECB CipherMode = "ecb"
	CFB CipherMode = "cfb"
	OFB CipherMode = "ofb"
	CTR CipherMode = "ctr"
)

// CipherPadding 定义填充模式枚举类型
type CipherPadding string

// 填充模式常量
const (
	No       CipherPadding = "no"
	Empty    CipherPadding = "empty"
	Zero     CipherPadding = "zero"
	PKCS5    CipherPadding = "pkcs5"
	PKCS7    CipherPadding = "pkcs7"
	AnsiX923 CipherPadding = "ansi-x-923"
	ISO97971 CipherPadding = "iso9797-1"
)

// Cipher defines a Cipher struct.
// 定义 Cipher 结构体
type Cipher struct {
	Mode    CipherMode
	Padding CipherPadding
	Keys    [2]string // 密钥+向量或者公钥+私钥模式
}

// NewCipher returns a new Cipher instance.
// 初始化 Cipher 结构体
func NewCipher() *Cipher {
	return &Cipher{
		Mode:    CBC,
		Padding: PKCS7,
		Keys:    [2]string{"", ""},
	}
}

func (c *Cipher) SetMode(mode CipherMode) {
	c.Mode = mode
}

func (c *Cipher) SetPadding(padding CipherPadding) {
	c.Padding = padding
}

func (c *Cipher) SetKey(index int, key string) {
	if len(c.Keys[index]) == 0 {
		c.Keys[index] = key
		return
	}
}

func (c *Cipher) Cipher() *dongle.Cipher {
	c1 := dongle.NewCipher()
	setCipherMode(c1, c.Mode)
	setCipherPadding(c1, c.Padding)
	c1.SetKey(c.Keys[0])
	if len(c.Keys[1]) > 0 {
		c1.SetIV(c.Keys[1])
	}
	return c1
}

func (c *Cipher) GetKey(index int) []byte {
	return string2bytes(c.Keys[index])
}

// string2bytes 将字节切片转换为字符串
func string2bytes(s string) []byte {
	return unsafe.Slice(unsafe.StringData(s), len(s))
}

// 设置加密分组模式
func setCipherMode(c *dongle.Cipher, mode CipherMode) {
	switch mode {
	case CBC:
		c.SetMode(dongle.CBC)
	case ECB:
		c.SetMode(dongle.ECB)
	case CFB:
		c.SetMode(dongle.CFB)
	case OFB:
		c.SetMode(dongle.OFB)
	case CTR:
		c.SetMode(dongle.CTR)
	default:
		c.SetMode(dongle.CBC)
	}
}

// 设置加密分组填充
func setCipherPadding(c *dongle.Cipher, padding CipherPadding) {
	switch padding {
	case No:
		c.SetPadding(dongle.No)
	case Empty:
		c.SetPadding(dongle.Empty)
	case Zero:
		c.SetPadding(dongle.Zero)
	case PKCS5:
		c.SetPadding(dongle.PKCS5)
	case PKCS7:
		c.SetPadding(dongle.PKCS7)
	case AnsiX923:
		c.SetPadding(dongle.AnsiX923)
	case ISO97971:
		c.SetPadding(dongle.ISO97971)
	default:
		c.SetPadding(dongle.PKCS7)
	}
}
